﻿using System;
using System.Collections.Generic;
using System.ServiceProcess;
using System.Text;
using Reactor.Messages.Events.Health;
using Reactor.ServiceGrid.Resources;
using Reactor.ServiceGrid.Workflows.Contexts;
using Reactor.Workflow;
using Samurai.Wakizashi.Validation;
using Samurai.Wakizashi.Workflow;

namespace Reactor.ServiceGrid.Workflows
{
    public class ServicesMonitorWorkflow : SequentialWorkflow<MonitorContext>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ServicesMonitorWorkflow"/> class.
        /// </summary>
        /// <param name="workflowContext">The workflow context.</param>
        public ServicesMonitorWorkflow(MonitorContext workflowContext)
        {
            if (workflowContext == null) throw new ArgumentNullException("workflowContext");
            Context = workflowContext;

            var contextValidationStep = new ContextValidationStep<MonitorContext>(this);
            contextValidationStep.For(c => c.ReactorCore).Ensure().IsNotNull();
            contextValidationStep.For(c => c.CoreDataProvider).Ensure().IsNotNull();
            contextValidationStep.For(c => c.Logger).Ensure().IsNotNull();

            // Obtain monitor interval
            var rawValue = Environment.Context.Configuration.GetConfigurationValue(ServiceGridResources.Config_ServiceMonitorInterval);
            TimeSpan interval;
            if (!TimeSpan.TryParse(rawValue, out interval))
                interval = TimeSpan.FromMinutes(1);

            RegisterStep(contextValidationStep, ThreadType.Task);
            RegisterStep(new SuspendWorkflowStep<MonitorContext>(interval));
            RegisterStep(ObtainServiceControllers);
            RegisterStep(CheckServices);
            RegisterStep(UpdateLastSeenTimestamps);
            RegisterStep(ReportOverallStatus);
        }

        internal void ObtainServiceControllers()
        {
            Context.WindowsServiceControllers = new Queue<ServiceController>(Context.ReactorCore.GetServiceControllers());
        }

        internal void CheckServices()
        {
            Context.Logger.Debug("Monitoring managed services...");

            // Check Windows Service status and add any offline services to our list of known offline services.
            ServiceController serviceController = Context.WindowsServiceControllers.Dequeue();
            while (serviceController != null)
            {
                serviceController.Refresh();

                if (IsServiceInNegativeState(serviceController))
                {
                    Context.KnownOnlineServices.Remove(serviceController);

                    if (!Context.KnownOfflineServices.Contains(serviceController))
                        Context.KnownOfflineServices.Add(serviceController);
                }
                else
                {
                    Context.KnownOfflineServices.Remove(serviceController);

                    if(!Context.KnownOnlineServices.Contains(serviceController))
                        Context.KnownOnlineServices.Add(serviceController);
                }

                serviceController = (Context.WindowsServiceControllers.Count > 0) 
                                        ? Context.WindowsServiceControllers.Dequeue()
                                        : null;
            }
        }

        internal void UpdateLastSeenTimestamps()
        {
            ServiceIdentifier serviceIdentifier;
            foreach (var serviceController in Context.KnownOnlineServices)
            {
                if (ServiceIdentifier.TryParseFromServiceInstanceString(serviceController.ServiceName, out serviceIdentifier))
                    Context.CoreDataProvider.UpdateServiceLastSeenTimestamp(serviceIdentifier);
                else if(Context.Logger.IsWarnEnabled)
                    Context.Logger.WarnFormat("Unable to parse ServiceIdentifier from ServiceController name: {0}", serviceController.ServiceName);
            }

            Context.CoreDataProvider.UpdateCoreLastSeenTimestamp(Context.ReactorCore.Identifier);
        }

        internal void ReportOverallStatus()
        {
            var msg = new StringBuilder();
            msg.Append("The following services were discovered offline:");
            msg.Append(System.Environment.NewLine);
            if (Context.KnownOfflineServices.Count == 0)
            {
                msg.Append("\t--NONE--");
                msg.Append(System.Environment.NewLine);
            }
            else
            {
                foreach (var serviceController in Context.KnownOfflineServices)
                {
                    msg.Append("\t");
                    msg.Append(serviceController.ServiceName);
                    msg.Append(System.Environment.NewLine);
                }
            }

            msg.Append("The following services were discovered online:");
            msg.Append(System.Environment.NewLine);
            if (Context.KnownOnlineServices.Count == 0)
            {
                msg.Append("\t--NONE--");
                msg.Append(System.Environment.NewLine);
            }
            else
            {
                foreach (var serviceController in Context.KnownOnlineServices)
                {
                    msg.Append("\t");
                    msg.Append(serviceController.ServiceName);
                    msg.Append(System.Environment.NewLine);
                }
            }

            // Construct final message
            var finalMessage = msg.ToString();

            // Log it if necessary
            if (Context.Logger.IsDebugEnabled)
                Context.Logger.Debug(finalMessage);

            // Send advisory message containing all the offline services
            var healthReport = new CoreHealthReport
                                   {
                                       Subject = string.Format("Health Report for Core: {0}", Context.ReactorCore.Identifier),
                                       Description = finalMessage
                                   };
            Environment.Context.ServiceBus.Send(healthReport);
        }

        private static bool IsServiceInNegativeState(ServiceController serviceController)
        {
            switch (serviceController.Status)
            {
                case ServiceControllerStatus.StartPending:
                case ServiceControllerStatus.Running:
                case ServiceControllerStatus.ContinuePending:
                    return false;
                case ServiceControllerStatus.Paused:
                case ServiceControllerStatus.PausePending:
                case ServiceControllerStatus.Stopped:
                case ServiceControllerStatus.StopPending:
                    return true;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
}
